"""
服务接口基类模块
"""

import json
import logging
import os
from abc import ABC, abstractmethod

from jlpay.core.http import DefaultHttpClientBuilder, HttpRequest
from jlpay.core.sign import SignVerifierManager
from jlpay.exception.exceptions import HttpExecutionException, SignVerifyException, ConfigException, CryptoException
from jlpay.model.base import BaseResponse
from jlpay.utils.sm_utils import sm2_sign, sm2_encrypt, sm2_decrypt, sm4_ecb_encrypt, sm4_ecb_decrypt, generate_sm4_key
from jlpay.utils.annotation_utils import get_sensitive_fields, requires_encrypt, requires_decrypt, encrypt_nested_fields

# 创建日志记录器
logger = logging.getLogger("jlpay.service")

class BaseService(ABC):
    """服务接口基类"""
    
    def __init__(self, config):
        """
        初始化服务接口
        
        Args:
            config: OrgConfig实例
        """
        self.config = config
        self.http_client = DefaultHttpClientBuilder.build(
            config.connect_timeout, config.read_timeout)
        self.sign_verifier = SignVerifierManager.get_verifier(config)
    
    @abstractmethod
    def get_api_url(self, request_class, api_path=None):
        """
        获取API URL
        
        Args:
            request_class: 请求类
            api_path: 接口路径
            
        Returns:
            完整的API URL
        """
        pass
    
    def post(self, request, response_class, api_path=None):
        """
        发送POST请求
        
        Args:
            request: 请求对象
            response_class: 响应类
            api_path: 接口路径
            
        Returns:
            响应对象
            
        Raises:
            ConfigException: 配置异常
            HttpExecutionException: HTTP执行异常
            SignVerifyException: 签名验证异常
        """
        return self._execute(request, response_class, "POST", api_path)

    def get(self, request, response_class, api_path=None):
        """
        发送GET请求
        
        Args:
            request: 请求对象
            response_class: 响应类
            api_path: 接口路径
            
        Returns:
            响应对象
            
        Raises:
            ConfigException: 配置异常
            HttpExecutionException: HTTP执行异常
            SignVerifyException: 签名验证异常
        """
        return self._execute(request, response_class, "GET", api_path)
    
    def _execute(self, request, response_class, method, api_path=None):
        """
        执行HTTP请求
        
        Args:
            request: 请求对象
            response_class: 响应类
            method: HTTP方法
            api_path: 接口路径
            
        Returns:
            响应对象
            
        Raises:
            ConfigException: 配置异常
            HttpExecutionException: HTTP执行异常
            SignVerifyException: 签名验证异常
        """
        # 校验请求参数
        request.check_required_params(self.config)
        
        # 构建完整URL
        url = self.get_api_url(request.__class__, api_path)
        
        # 获取URI
        uri = url.split('.com')[1] if '.com' in url else url.split('://', 1)[1].split('/', 1)[1]
        
        # 处理请求加密
        crypto_headers = {}
        try:
            self._handle_encrypt(request, crypto_headers)
        except Exception as e:
            logger.error(f"处理请求加密时发生错误: {str(e)}", exc_info=True)
            raise CryptoException(f"处理请求加密失败: {str(e)}", e)
        
        # 将请求对象转换为字典 (移动到加密处理之后)
        body = request.to_dict()
        
        # 签名
        sign_value, timestamp, nonce = self.sign_verifier.sign(method, uri, body)
        
        # 构建HTTP请求
        builder = HttpRequest.builder()
        builder.url(url)
        builder.method(method)
        builder.header("Content-Type", "application/json; charset=utf-8")
        builder.header("x-jlpay-appid", self.config.app_id)
        builder.header("x-jlpay-nonce", nonce)
        builder.header("x-jlpay-timestamp", timestamp)
        builder.header("x-jlpay-sign-alg", "SM3WithSM2WithDer")
        builder.header("x-jlpay-sign", sign_value)
        
        # 添加必需的请求头
        # 从请求数据中提取商户号
        merch_no = body.get('merch_no') or body.get('mch_id')
        if merch_no:
            builder.header("x-jlpay-merchno", merch_no)
        
        # 从请求数据中提取机构号
        org_no = body.get('org_no')
        if org_no:
            builder.header("x-jlpay-orgno", org_no)
        
        # 添加加密相关的请求头
        logger.debug(f"加密相关的请求头: {crypto_headers}")
        for key, value in crypto_headers.items():
            builder.header(key, value)
            
        builder.body(body)
        http_request = builder.build()
        # 打印HTTP请求头和内容
        logger.debug("=== HTTP请求信息 ===")
        logger.debug(f"请求URL: {url}")
        logger.debug(f"请求方法: {method}")
        logger.debug("请求头:")
        for key, value in http_request.headers.items():
            logger.debug(f"  {key}: {value}")
        logger.debug(f"请求体: {json.dumps(body, ensure_ascii=False)}")
        logger.debug("==================")
        
        # 执行请求
        response = self.http_client.execute(http_request)
        
        # 验证响应签名
        if "x-jlpay-sign" in response.headers:
            self.sign_verifier.verify(
                method, 
                uri, 
                response.headers.get("x-jlpay-timestamp"), 
                response.headers.get("x-jlpay-nonce"), 
                response.body, 
                response.headers.get("x-jlpay-sign")
            )
        
        # 解析响应
        try:
            response_dict = json.loads(response.body)
            
            # 如果传入的是BaseResponse类，直接使用它，否则创建具体的响应类实例
            response_obj = BaseResponse(response_dict) if response_class == BaseResponse else response_class(response_dict)
            
            # 处理响应解密
            try:
                self._handle_decrypt(response_obj, response.headers)
            except Exception as e:
                logger.error(f"处理响应解密时发生错误: {str(e)}", exc_info=True)
                # 仅记录错误，不影响正常流程
            
            return response_obj
        except json.JSONDecodeError as e:
            logger.error(f"解析响应JSON失败: {str(e)}, 原始响应: {response.body[:200]}...", exc_info=True)
            raise HttpExecutionException(f"解析响应JSON失败: {str(e)}", e)

    def _handle_encrypt(self, request, headers):
        """
        处理请求加密
        
        Args:
            request: 请求对象
            headers: 请求头字典，用于添加加密相关的头信息
        """
        # 打印加密相关配置信息，用于诊断
        auto_encrypt = getattr(self.config, 'auto_encrypt', False)
        crypto_alg = getattr(self.config, 'crypto_alg', '')
        logger.debug(f"加密配置信息: auto_encrypt={auto_encrypt}, crypto_alg={crypto_alg}")
            
        # 使用注解工具类检查是否需要加密
        if not requires_encrypt(request):
            logger.debug("请求类未标记为需要加密，跳过加密处理")
            return
        
        # 获取敏感字段
        sensitive_fields = get_sensitive_fields(request)
        if not sensitive_fields:
            # 兼容旧版实现
            if hasattr(request, 'get_sensitive_fields'):
                sensitive_fields = request.get_sensitive_fields()
            
        if not sensitive_fields:
            logger.debug("未发现需要加密的敏感字段，跳过加密处理")
            return
        else:
            logger.debug(f"发现需要加密的敏感字段: {sensitive_fields}")
            
        # 检查是否启用自动加密
        if not auto_encrypt:
            logger.warning("警告: 发现敏感字段但自动加密未启用！建议启用auto_encrypt=True")
            # 即使未启用加密，也添加加密头信息，确保服务端能够正确处理请求
            headers["x-jlpay-crypto-alg"] = "SM2WithSM4"
            # 在未启用加密的情况下不加密数据，仅添加标记头
            return
            
        # 检查是否支持SM2WithSM4加密
        if crypto_alg != "SM2WithSM4":
            logger.warning(f"警告: 不支持的加密算法: {crypto_alg}，建议设置crypto_alg='SM2WithSM4'")
            # 即使算法不匹配，也添加加密头信息
            headers["x-jlpay-crypto-alg"] = "SM2WithSM4"
            return
            
        try:
            # 生成SM4密钥
            sm4_key = generate_sm4_key()
            
            # 将请求对象转换为字典
            request_dict = request.to_dict()
            
            # 定义加密函数
            def encrypt_value(value):
                return sm4_ecb_encrypt(value, sm4_key)
            
            # 判断是否包含嵌套敏感字段
            has_nested_fields = any('.' in str(field) for field in sensitive_fields)
            
            if has_nested_fields:
                # 使用嵌套字段加密处理
                encrypted_dict = encrypt_nested_fields(request_dict, sensitive_fields, encrypt_value)
                
                # 更新请求对象的属性
                for key, value in encrypted_dict.items():
                    if hasattr(request, key):
                        setattr(request, key, value)
            else:
                # 使用原有的直接字段加密处理
                for field in sensitive_fields:
                    field_name = field if isinstance(field, str) else field.field_path
                    if field_name in request_dict and request_dict[field_name] is not None:
                    # 将字段值转换为JSON字符串（如果是复杂对象）或保持原值（如果是简单类型）
                        value = request_dict[field_name]
                    if isinstance(value, (dict, list)):
                        value = json.dumps(value, ensure_ascii=False)
                    
                    # 使用SM4加密
                    try:
                        encrypted_value = sm4_ecb_encrypt(value, sm4_key)
                        setattr(request, field_name, encrypted_value)
                    except Exception as e:
                            logger.error(f"加密字段 {field_name} 失败: {str(e)}", exc_info=True)
                            raise CryptoException(f"加密字段 {field_name} 失败: {str(e)}", e)
            
            # 使用SM2加密SM4密钥
            try:
                encrypted_key = sm2_encrypt(sm4_key, self.config.jlpay_pub_key_hex)
                
                # 将加密后的密钥添加到请求头
                headers["x-jlpay-crypto-alg"] = "SM2WithSM4"
                headers["x-jlpay-key"] = encrypted_key
                
                # 打印关键信息以确认头部设置成功
                logger.debug(f"加密密钥已添加到请求头: x-jlpay-key={encrypted_key[:10]}...")
            except Exception as e:
                logger.error(f"加密SM4密钥失败: {str(e)}", exc_info=True)
                raise CryptoException(f"加密SM4密钥失败: {str(e)}", e)
        except Exception as e:
            logger.error(f"请求加密过程发生错误: {str(e)}", exc_info=True)
            raise CryptoException(f"请求加密失败: {str(e)}", e)
    
    def _handle_decrypt(self, response, headers):
        """
        处理响应解密
        
        Args:
            response: 响应对象
            headers: 响应头字典
        """
        # 检查是否启用自动解密
        if not getattr(self.config, 'auto_decrypt', False):
            return
            
        # 检查是否支持SM2+SM4解密
        crypto_alg = headers.get("x-jlpay-crypto-alg")
        if crypto_alg != "SM2WithSM4":
            return
            
        # 使用注解工具类检查是否需要解密
        if not requires_decrypt(response):
            # 兼容旧版实现，成功响应都可能需要解密
            if hasattr(response, 'is_success') and not response.is_success():
                return
        else:
            # 如果明确标记了需要解密，但响应不成功，也不进行解密
            if hasattr(response, 'is_success') and not response.is_success():
                return
            
        # 获取加密的密钥
        encrypted_key = headers.get("x-jlpay-key")
        if not encrypted_key:
            return
            
        try:
            # 使用SM2解密密钥
            sm4_key = sm2_decrypt(encrypted_key, self.config.org_pri_key_hex, self.config.jlpay_pub_key_hex)
            
            # 获取原始响应数据
            response_dict = response.get_origin_data() if hasattr(response, 'get_origin_data') else None
            if not response_dict:
                return

            # 获取需要解密的敏感字段列表
            sensitive_fields = get_sensitive_fields(response)
            sensitive_field_names = [field.field_path for field in sensitive_fields] if sensitive_fields else []

            # 只对敏感字段进行解密
            for field, value in response_dict.items():
                # 只处理敏感字段
                if field in sensitive_field_names and isinstance(value, str) and value.strip():
                    # 检查是否是Base64编码的字符串（可能是加密数据）
                    try:
                        # 尝试Base64解码
                        import base64
                        decoded_bytes = base64.b64decode(value)
                        # 如果解码成功且长度合理，尝试解密
                        if len(decoded_bytes) > 0:
                            try:
                                decrypted_value = sm4_ecb_decrypt(value, sm4_key)
                                # 尝试解析JSON
                                try:
                                    json_value = json.loads(decrypted_value)
                                    setattr(response, field, json_value)
                                except json.JSONDecodeError:
                                    setattr(response, field, decrypted_value)
                                logger.debug(f"成功解密字段 {field}")
                            except Exception as e:
                                logger.error(f"解密字段 {field} 失败: {str(e)}",
                                             exc_info=True)
                    except Exception:
                        # Base64解码失败，说明不是加密数据，保持原值
                        pass
        except Exception as e:
            logger.error(f"响应解密过程发生错误: {str(e)}", exc_info=True)
            # 不抛出异常，避免影响正常业务流程
            # 解密失败不影响业务，只是保持原值